#pragma once


#include <array>
#include <utility>
#include <functional>
#include <type_traits>
#include <iterator>

#include "type_traits.hpp"
namespace cpp{

	namespace __array{

		template <class T, std::size_t N, size_t ... I>
		constexpr std::array<std::remove_cv_t<T>, N> to_array_impl(T(&a)[N], std::index_sequence<I...>)
		{
			return{ { a[I]... } };
		}

		template <typename _Up>
		struct __is_reference_wrapper : std::false_type{};

		template <typename _Up>
		struct __is_reference_wrapper<std::reference_wrapper<_Up>> :std::true_type{};
	}


	template <class _Dest = void, class... _Types, class T = std::conditional_t<std::is_void<_Dest>::value, std::common_type_t<_Types...>, _Dest> >
	constexpr std::array<T, sizeof...(_Types)>
		make_array(_Types&&... t)
	{
		static_assert(!std::is_void<_Dest>::value ||
					  !disjunction<
					  typename __array::__is_reference_wrapper< std::decay_t<_Types> > ::type ...
					  >::value,
					  "make_array cannot be used without an explicit target type "
					  "if any of the types given is a reference_wrapper");
		return{ std::forward<_Types>(t)... };
	}




	template <class T, std::size_t N>
	constexpr std::array<std::remove_cv_t<T>, N> to_array(T(&a)[N])
	{
		return __array::to_array_impl(a, std::make_index_sequence<N>());
	}


	template<class T, std::size_t... Rn>
	class multi_array;

	template<class T, std::size_t _Size, std::size_t... Rn>
	class multi_array<T, _Size, Rn...>
	{
	public:
		using value_type = multi_array< T, Rn...>;

		using iterator = value_type*;
		using const_iterator = const value_type*;
		typedef std::reverse_iterator<iterator> reverse_iterator;
		typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

		typedef value_type& reference;
		typedef const value_type& const_reference;

		using size_type = std::size_t;

		iterator begin()
		{
			return ::std::addressof(_data[0]);
		}

		const_iterator begin() const
		{
			return ::std::addressof(_data[0]);
		}

		iterator end()
		{
			return ::std::addressof(_data[0]) + _Size;
		}

		const_iterator end() const
		{
			return ::std::addressof(_data[0]) + _Size;
		}

		reverse_iterator rbegin()
		{
			return (reverse_iterator(end()));
		}

		const_reverse_iterator rbegin() const
		{
			return (const_reverse_iterator(end()));
		}

		reverse_iterator rend()
		{
			return (reverse_iterator(begin()));
		}

		const_reverse_iterator rend() const
		{
			return (const_reverse_iterator(begin()));
		}

		const_iterator cbegin() const
		{
			return (begin());
		}

		const_iterator cend() const
		{
			return (end());
		}

		const_reverse_iterator crbegin() const
		{
			return (rbegin());
		}

		const_reverse_iterator crend() const
		{
			return (rend());
		}

		size_type size() const
		{
			return (_Size);
		}

		size_type max_size() const
		{
			return (_Size);
		}

		bool empty() const
		{
			return (false);
		}


		reference at(size_type _Pos)
		{
			if(_Size <= _Pos)
				throw std::out_of_range("out of range");
			return (_data[_Pos]);
		}

		const_reference at(size_type _Pos) const
		{
			return (_Size <= _Pos
					? (throw std::out_of_range("out of range"), _data[_Pos])
					: _data[_Pos]);
		}

		value_type& operator[](size_t N)
		{
			return _data[N];
		}
		const value_type& operator[](size_t N) const
		{
			return _data[N];
		}

		reference front()
		{
			return (_data[0]);
		}

		const_reference front() const
		{
			return (_data[0]);
		}

		reference back()
		{
			return (_data[_Size - 1]);
		}

		const_reference back() const
		{
			return (_data[_Size - 1]);
		}

		value_type *data()
		{
			return (_data);
		}

		const value_type *data() const
		{
			return (_data);
		}

	public:
		value_type _data[_Size];
	};

	template<class T, std::size_t _Size>
	class multi_array<T, _Size>
	{
	public:
		using value_type = T;
		using iterator = value_type*;
		using const_iterator = const value_type*;
		typedef std::reverse_iterator<iterator> reverse_iterator;
		typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
		typedef value_type& reference;
		typedef const value_type& const_reference;
		using size_type = std::size_t;

		iterator begin()
		{
			return ::std::addressof(_data[0]);
		}

		const_iterator begin() const
		{
			return ::std::addressof(_data[0]);
		}

		iterator end()
		{
			return ::std::addressof(_data[0]) + _Size;
		}

		const_iterator end() const
		{
			return ::std::addressof(_data[0]) + _Size;
		}

		reverse_iterator rbegin()
		{
			return (reverse_iterator(end()));
		}

		const_reverse_iterator rbegin() const
		{
			return (const_reverse_iterator(end()));
		}

		reverse_iterator rend()
		{
			return (reverse_iterator(begin()));
		}

		const_reverse_iterator rend() const
		{
			return (const_reverse_iterator(begin()));
		}

		const_iterator cbegin() const
		{
			return (begin());
		}

		const_iterator cend() const
		{
			return (end());
		}

		const_reverse_iterator crbegin() const
		{
			return (rbegin());
		}

		const_reverse_iterator crend() const
		{
			return (rend());
		}

		size_type size() const
		{
			return (_Size);
		}

		size_type max_size() const
		{
			return (_Size);
		}

		bool empty() const
		{
			return (false);
		}


		reference at(size_type _Pos)
		{
			if(_Size <= _Pos)
				throw std::out_of_range("out of range");
			return (_data[_Pos]);
		}

		const_reference at(size_type _Pos) const
		{
			return (_Size <= _Pos
					? (throw std::out_of_range("out of range"), _data[_Pos])
					: _data[_Pos]);
		}

		value_type& operator[](size_t N)
		{
			return _data[N];
		}
		const value_type& operator[](size_t N) const
		{
			return _data[N];
		}

		reference front()
		{
			return (_data[0]);
		}

		const_reference front() const
		{
			return (_data[0]);
		}

		reference back()
		{
			return (_data[_Size - 1]);
		}

		const_reference back() const
		{
			return (_data[_Size - 1]);
		}

		value_type *data()
		{
			return (_data);
		}

		const value_type *data() const
		{
			return (_data);
		}

	public:
		value_type _data[_Size];
	};
}

